package com.leetcodehot.problems;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class problems18 {
    /**
     * 设 s=nums[a]+nums[a+1]+nums[a+2]+nums[a+3]。如果 s>target，由于数组已经排序，后面无论怎么选，选出的四个数的和不会比 s 还小，所以后面不会找到等于 target 的四数之和了。所以只要 s>target，就可以直接 break 外层循环了。
     * <p>
     * 设 s=nums[a]+nums[n−3]+nums[n−2]+nums[n−1]。如果 s<target，由于数组已经排序，nums[a] 加上后面任意三个数都不会超过 s，所以无法在后面找到另外三个数与 nums[a] 相加等于 target。但是后面还有更大的 nums[a]，可能出现四数之和等于 target 的情况，所以还需要继续枚举，continue 外层循环。
     * <p>
     * 如果 a>0 且 nums[a]=nums[a−1]，那么 nums[a] 和后面数字相加的结果，必然在之前算出过，所以无需执行后续代码，直接 continue 外层循环。（可以放在循环开头判断。）
     * <p>
     * 对于 nums[b] 的枚举（b 从 a+1 开始），也同样有类似优化：
     * <p>
     * 设 s=nums[a]+nums[b]+nums[b+1]+nums[b+2]。如果 s>target，由于数组已经排序，后面无论怎么选，选出的四个数的和不会比 s 还小，所以后面不会找到等于 target 的四数之和了。所以只要 s>target，就可以直接 break。
     * <p>
     * 设 s=nums[a]+nums[b]+nums[n−2]+nums[n−1]。如果 s<target，由于数组已经排序，nums[a]+nums[b] 加上后面任意两个数都不会超过 s，所以无法在后面找到另外两个数与 nums[a] 和 nums[b] 相加等于 target。但是后面还有更大的 nums[b]，可能出现四数之和等于 target 的情况，所以还需要继续枚举，continue。
     * <p>
     * 如果 b>a+1 且 nums[b]=nums[b−1]，那么 nums[b] 和后面数字相加的结果，必然在之前算出过，所以无需执行后续代码，直接 continue。注意这里 b>a+1 的判断是必须的，如果不判断，对于示例 2 这样的数据，会直接 continue，漏掉符合要求的答案。
     */
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        int n = nums.length;
        for (int a = 0; a < n - 3; a++) {
            if (a > 0 && nums[a] == nums[a - 1]) {
                continue;
            }
            if (0L + nums[a] + nums[a + 1] + nums[a + 2] + nums[a + 3] > target) break; // 优化一
            if (0L + nums[a] + nums[n - 3] + nums[n - 2] + nums[n - 1] < target) continue; // 优化二
            for (int b = a + 1; b < n - 2; b++) {
                if (b != a + 1 && nums[b] == nums[b - 1]) {
                    continue;
                }
                if (0L + nums[a] + nums[b] + nums[b + 1] + nums[b + 2] > target) break; // 优化一
                if (0L + nums[a] + nums[b] + nums[n - 2] + nums[n - 1] < target) continue; // 优化二
                //剩下两个数字
                int c = b + 1;
                int d = n - 1;
                while (c < d) {
                    if (0L + nums[a] + nums[b] + nums[c] + nums[d] == target) {
                        res.add(Arrays.asList(nums[a], nums[b], nums[c], nums[d]));
                        c++;
                        while (c < d && nums[c] == nums[c - 1]) {
                            c++;
                        }
                        d--;
                        while (c < d && nums[d] == nums[d + 1]) {
                            d--;
                        }
                    } else if (nums[a] + nums[b] + nums[c] + nums[d] < target) {
                        c++;
                    } else {
                        d--;
                    }
                }
            }
        }
        return res;
    }
}
